#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
动态量能阈值计算器

基于历史百分位数的动态计算，更适应个股特性和市场环境
支持统一缓存机制，供分时回测、实盘、模拟盘、监控使用

⭐ 核心算法：
  - 动态阈值 = 历史95%分位数 / 20日均量
  - 使用20日均量作为基准，避免连续放量场景下5日均量被拉高导致阈值失效
"""

import numpy as np
import logging
from typing import Dict, Optional, Tuple

logger = logging.getLogger(__name__)


class DynamicVolumeThresholdCalculator:
    """动态量能阈值计算器"""
    
    @staticmethod
    def get_required_history_days(config: Dict) -> int:
        """
        获取所需的最小历史数据天数（用于自动延长数据加载起始日期）
        
        Args:
            config: 配置字典，包含dynamic_volume_threshold配置
        
        Returns:
            int: 所需的最小历史数据天数（自然日，考虑交易日约1.5倍）
        """
        dynamic_config = config.get('dynamic_volume_threshold', {})
        enabled = dynamic_config.get('enabled', False)
        
        if not enabled:
            # 未启用动态阈值，使用默认的历史数据需求（30天用于均线计算）
            return 60  # 30个交易日约45自然日，预留一些余量
        
        # 启用动态阈值，需要lookback_days + 额外余量
        lookback_days = dynamic_config.get('lookback_days', 60)
        # 考虑交易日与自然日的比例（约1.5倍），加上额外余量（20天）
        required_days = int(lookback_days * 1.5) + 20
        
        return required_days
    
    def __init__(self, config: Dict):
        """
        初始化动态阈值计算器
        
        Args:
            config: 配置字典，包含dynamic_volume_threshold配置
        """
        self.config = config.get('dynamic_volume_threshold', {})
        self.enabled = self.config.get('enabled', False)
        
        # 核心参数
        self.lookback_days = self.config.get('lookback_days', 60)
        self.percentile = self.config.get('percentile', 95)
        
        # 基础固定阈值（用于回退和下限保护）
        self.base_thresholds = self.config.get('base_thresholds', {
            'large': 4.5,
            'mid': 5.5,
            'small': 6.5
        })
        
        # 自适应参数
        adaptive_config = self.config.get('adaptive', {})
        self.volatility_adjustment = adaptive_config.get('volatility_adjustment', True)
        self.blend_strategy = adaptive_config.get('blend_strategy', 'balanced')
        self.fallback_enabled = adaptive_config.get('fallback_enabled', True)
        
        # ⭐ 统一缓存（key: stock_code, value: 阈值计算结果）
        self._threshold_cache: Dict[str, Dict] = {}
        
        logger.info(f"动态阈值计算器初始化: enabled={self.enabled}, lookback={self.lookback_days}天, percentile={self.percentile}%")
    
    def get_extreme_volume_threshold(self, stock: str, hist_np: Dict, stock_type: str) -> Dict:
        """
        获取天量阈值（统一入口，支持缓存）
        
        Args:
            stock: 股票代码
            hist_np: 历史数据（numpy格式）
            stock_type: 股票类型（large/mid/small）
        
        Returns:
            dict: {
                'volume_ratio_threshold': float,  # 量比阈值
                'vol_ma20_ratio_threshold': float,  # 20日均量倍数阈值
                'absolute_threshold': float,  # 绝对成交量阈值
                'method': str,  # 计算方法: 'dynamic' 或 'fixed'
                'cache_key': str  # 缓存键
            }
        """
        # 如果未启用，返回固定阈值
        if not self.enabled:
            return self._get_fixed_threshold(stock_type)
        
        # ⭐ 生成缓存键（基于历史数据的关键特征）
        cache_key = self._generate_cache_key(stock, hist_np)
        
        # 检查缓存
        if stock in self._threshold_cache:
            cached = self._threshold_cache[stock]
            if cached.get('cache_key') == cache_key:
                # 缓存命中
                logger.debug(f"{stock} 动态阈值缓存命中")
                return cached['result']
        
        # 计算动态阈值
        result = self._calculate_dynamic_threshold_v2(hist_np, stock_type)
        result['method'] = 'dynamic'
        result['cache_key'] = cache_key
        
        # 更新缓存
        self._threshold_cache[stock] = {
            'result': result,
            'cache_key': cache_key
        }
        
        return result
    
    def _generate_cache_key(self, stock: str, hist_np: Dict) -> str:
        """
        生成缓存键（基于历史数据的关键特征）
        
        使用历史数据的统计特征作为缓存键，当历史数据变化时自动失效
        ⭐ 关键：使用lookback_days范围内的数据特征，确保阈值计算基于的数据一致
        """
        if hist_np is None or 'volume' not in hist_np or len(hist_np['volume']) < 10:
            return f"{stock}_insufficient_data"
        
        volumes = hist_np['volume']
        
        # 使用lookback_days范围内的数据特征（与计算阈值时使用的数据范围一致）
        lookback_count = min(self.lookback_days, len(volumes) - 1)  # 排除最后一天（当前判断日）
        if lookback_count < 10:
            lookback_count = min(10, len(volumes) - 1)
        
        # 获取用于计算阈值的历史数据范围
        historical_volumes = volumes[-lookback_count-1:-1] if len(volumes) > lookback_count + 1 else volumes[:-1]
        
        if len(historical_volumes) < 5:
            return f"{stock}_insufficient_data"
        
        # 计算关键特征值（用于检测数据变化）
        # 使用均值、中位数、最大值、分位数作为特征
        hist_mean = np.mean(historical_volumes)
        hist_median = np.median(historical_volumes)
        hist_max = np.max(historical_volumes)
        hist_p95 = np.percentile(historical_volumes, 95)
        
        # 生成缓存键（使用整数哈希，避免过长）
        # 格式：股票代码_均值_中位数_最大值_95分位数
        cache_key = f"{stock}_{int(hist_mean)}_{int(hist_median)}_{int(hist_max)}_{int(hist_p95)}"
        return cache_key
    
    def _calculate_dynamic_threshold_v2(self, hist_np: Dict, stock_type: str) -> Dict:
        """
        优化版动态天量阈值计算
        
        明确分离绝对成交量阈值和相对量比阈值
        
        Args:
            hist_np: 历史数据（numpy格式）
            stock_type: 股票类型（large/mid/small）
        
        Returns:
            dict: 阈值计算结果
        """
        # 确保使用历史数据（不包含当前判断日）
        if hist_np is None or 'volume' not in hist_np:
            return self._get_fallback_threshold(stock_type)
        
        volumes = hist_np['volume']
        
        if len(volumes) < self.lookback_days + 1:
            # 数据不足，使用回退机制
            if self.fallback_enabled:
                return self._get_fallback_threshold(stock_type)
            else:
                # 调整回看天数
                lookback_days = len(volumes) - 1
                if lookback_days < 10:
                    return self._get_fallback_threshold(stock_type)
        else:
            lookback_days = self.lookback_days
        
        # 使用历史数据（排除最后一天，即当前判断日）
        historical_volumes = volumes[-lookback_days-1:-1] if len(volumes) > lookback_days + 1 else volumes[:-1]
        
        if len(historical_volumes) < lookback_days * 0.8:  # 数据不足80%
            return self._get_fallback_threshold(stock_type)
        
        # 1. 计算绝对成交量阈值（历史百分位数）
        absolute_threshold = float(np.percentile(historical_volumes, self.percentile))
        
        # 2. 计算均量基准（用于动态阈值计算）
        # ⭐ 使用20日均量作为动态阈值计算基准（避免连续放量场景下5日均量被拉高导致阈值失效）
        vol_ma20 = float(np.mean(historical_volumes[-20:])) if len(historical_volumes) >= 20 else float(np.mean(historical_volumes))
        vol_ma5 = float(np.mean(historical_volumes[-5:])) if len(historical_volumes) >= 5 else float(np.mean(historical_volumes))
        
        # 3. 计算动态量比阈值（使用20日均量作为基准）
        dynamic_volume_ratio_threshold = absolute_threshold / vol_ma20 if vol_ma20 > 0 else 0
        
        # 4. 智能下限保护（基于历史分布）
        min_threshold = self._calculate_adaptive_min_threshold(historical_volumes, stock_type, vol_ma20)
        
        # 5. 最终阈值（动态调整，非简单取大）
        final_threshold = self._blend_thresholds(dynamic_volume_ratio_threshold, min_threshold, stock_type)
        
        # 6. 计算20日均量倍数阈值
        # ⭐ vol_ma20_ratio_threshold 直接使用动态原始值（不经过下限保护）
        #    因为策略判断条件2：current_volume > vol_ma20 * vol_ma20_ratio_threshold
        #    这个条件已经是正确的20日均量体系，应该使用原始动态阈值
        # ⭐ volume_ratio_threshold 使用下限保护后的值（兼容策略中的5日均量判断）
        vol_ma20_ratio = dynamic_volume_ratio_threshold  # 使用原始动态值
        
        return {
            'absolute_threshold': absolute_threshold,           # 绝对成交量阈值
            'volume_ratio_threshold': final_threshold,        # 量比阈值（经过下限保护，用于5日均量判断）
            'vol_ma20_ratio_threshold': vol_ma20_ratio,       # 20日均量倍数阈值（原始动态值，用于20日均量判断）
            'vol_ma5': vol_ma5,                               # 5日均量基准
            'vol_ma20': vol_ma20,                             # 20日均量基准
            'dynamic_raw': dynamic_volume_ratio_threshold,     # 原始动态值
            'min_threshold': min_threshold,                    # 使用的最小阈值
            'percentile_value': absolute_threshold             # 百分位数对应的成交量
        }
    
    def _calculate_adaptive_min_threshold(self, historical_volumes: np.ndarray, stock_type: str, vol_ma20: float) -> float:
        """
        基于历史分布计算自适应下限
        
        Args:
            historical_volumes: 历史成交量数组
            stock_type: 股票类型
            vol_ma20: 20日均量（动态阈值计算基准）
        
        Returns:
            float: 自适应最小阈值
        """
        if len(historical_volumes) < 10:
            return self.base_thresholds.get(stock_type, 4.5)
        
        # 方案：基于历史中位数和75%分位数
        median_volume = float(np.median(historical_volumes))
        p75_volume = float(np.percentile(historical_volumes, 75))
        
        # 中位数相对量比（使用20日均量作为基准）
        median_ratio = median_volume / vol_ma20 if vol_ma20 > 0 else 0
        
        # 固定下限（基于股票类型）
        fixed_min = self.base_thresholds.get(stock_type, 4.5)
        
        # 自适应下限：取历史分布和固定值的较大者（更保守）
        # 使用中位数的1.5-2倍作为下限
        adaptive_min = max(median_ratio * 1.8, fixed_min * 0.9)
        
        return adaptive_min
    
    def _blend_thresholds(self, dynamic_threshold: float, min_threshold: float, stock_type: str) -> float:
        """
        智能混合动态阈值和最小阈值
        
        Args:
            dynamic_threshold: 动态计算的阈值
            min_threshold: 最小阈值
            stock_type: 股票类型
        
        Returns:
            float: 混合后的最终阈值
        """
        if dynamic_threshold <= 0:
            return min_threshold
        
        # 根据混合策略选择
        if self.blend_strategy == 'conservative':
            # 保守策略：取较大值
            return max(dynamic_threshold, min_threshold)
        
        elif self.blend_strategy == 'aggressive':
            # 激进策略：更信任动态值
            if dynamic_threshold > min_threshold * 1.3:
                return dynamic_threshold
            else:
                return max(dynamic_threshold * 0.9, min_threshold)
        
        else:  # balanced
            # 平衡策略：加权平均
            if dynamic_threshold > min_threshold * 1.2:
                # 动态值明显更高，使用动态值（更敏感）
                return dynamic_threshold
            elif dynamic_threshold > min_threshold:
                # 动态值略高，加权平均（平衡）
                weight = 0.7  # 动态值权重
                return dynamic_threshold * weight + min_threshold * (1 - weight)
            else:
                # 动态值低于最小值，使用最小值（保守）
                return min_threshold
    
    def _get_fallback_threshold(self, stock_type: str) -> Dict:
        """
        数据不足时的回退阈值（使用固定值）
        
        Args:
            stock_type: 股票类型
        
        Returns:
            dict: 固定阈值结果
        """
        base_threshold = self.base_thresholds.get(stock_type, 4.5)
        
        # vol_ma20_ratio 使用固定比例（基于之前的配置）
        vol_ma20_ratios = {
            'large': 3.5,
            'mid': 4.5,
            'small': 5.5
        }
        
        return {
            'absolute_threshold': 0,  # 数据不足，无法计算
            'volume_ratio_threshold': base_threshold,
            'vol_ma20_ratio_threshold': vol_ma20_ratios.get(stock_type, 3.5),
            'vol_ma5': 0,
            'vol_ma20': 0,
            'dynamic_raw': 0,
            'min_threshold': base_threshold,
            'percentile_value': 0,
            'method': 'fixed_fallback'
        }
    
    def _get_fixed_threshold(self, stock_type: str) -> Dict:
        """
        获取固定阈值（动态功能未启用时使用）
        
        Args:
            stock_type: 股票类型
        
        Returns:
            dict: 固定阈值结果
        """
        base_threshold = self.base_thresholds.get(stock_type, 4.5)
        
        vol_ma20_ratios = {
            'large': 3.5,
            'mid': 4.5,
            'small': 5.5
        }
        
        return {
            'absolute_threshold': 0,
            'volume_ratio_threshold': base_threshold,
            'vol_ma20_ratio_threshold': vol_ma20_ratios.get(stock_type, 3.5),
            'vol_ma5': 0,
            'vol_ma20': 0,
            'dynamic_raw': 0,
            'min_threshold': base_threshold,
            'percentile_value': 0,
            'method': 'fixed'
        }
    
    def clear_cache(self, stock: Optional[str] = None):
        """
        清除缓存
        
        Args:
            stock: 股票代码，如果为None则清除所有缓存
        """
        if stock is None:
            self._threshold_cache.clear()
            logger.debug("清除所有动态阈值缓存")
        elif stock in self._threshold_cache:
            del self._threshold_cache[stock]
            logger.debug(f"清除 {stock} 的动态阈值缓存")

